//
//  WmcLocationManager.swift
//  YPWatermarkCamera
//
//  Created by 周诗松 on 2022/4/9.
//

import RxRelay
import YPBedrock
import YPNetwork
import Foundation
import AMapSearchKit
import AMapLocationKit
import AMapFoundationKit

// MARK: - [WmcLocationManager]

open class WmcLocationManager: NSObject {
    // Lifecycle

    override init() {
        super.init()
        self.bindEvent()
    }

    // Open

    /// GPS定位
    open private(set) var locationStatus: BehaviorRelay<(status: WmcAmapLocationManager.Status, data: WmcLocationModel?)?> = .init(value: nil)

    /// 获取poi状态
    open private(set) var poiStatus: BehaviorRelay<(status: WmcLocationManager.PoiStatus, data: WmcSelectAddressModel?)?> = .init(value: nil)

    /// 授权状态
    open private(set) var locationAuthorizationPublish: PublishRelay<Bool> = .init()

    /// 附近的poi列表
    open private(set) var poiList: BehaviorRelay<[WmcSelectAddressModel]> = .init(value: [])

    /// 地址前缀
    open private(set) var addressPrefix: String?

    /// 首次获取poi，首页定位图标需要显示状态
    open private(set) var firstFetchPoi: Bool = true

    /// 自定义的默认poi
    open private(set) var defaultPoi: WmcSelectAddressModel?

    // Public

    /// 获取POI的状态
    public enum PoiStatus {
        case updatingPoi
        /// POI获取成功，并自动设置POI
        case updatePoiCompletion
        /// POI获取失败
        case updatePoiFailure
    }

    public static var shared: WmcLocationManager = {
        let manager = WmcLocationManager()
        return manager
    }()

    // Internal

    /// 高德隐私合规
    static func requestPrivecy() {
        WmcAmapLocationManager.requestPrivecy()
        WmcAMapSearchManager.requestPrivecy()
    }

    /// 开启定位
    static func startLocation(successBlock: (() -> Void)? = nil) {
        let type = YPNetworkReachability.queryNetworkReachabilityType()
        if type == .notReachable || type == .unknow {
            shared.networkInterruption = true
        }

        // 查询常用地址
        WmcLocationManager.shared.queryCommonlyAddress()
        // 查询自定义地址
        WmcLocationManager.shared.queryCustomAddress()
        // 开启定位
        WmcAmapLocationManager.startUpdateLocation() {
            successBlock?()
        }
    }

    /// 停止定位
    static func stopLocation() {
        WmcAmapLocationManager.stopUpdateLocation()
        shared.clean()
    }

    /// 从选择地址列表，手动选中POI
    static func setSelectedPOI(_ poi: WmcSelectAddressModel) {
        let setPoid: () -> Void = {
            shared.poiStatus.accept((status: .updatePoiCompletion, data: poi))
            shared.manualSelectedPoi = poi

            let list: [WmcSelectAddressModel] = shared.poiList.value.map { model in
                let temp = model
                temp.isSelected = poi.poi.uid == model.poi.uid
                return temp
            }
            shared.poiList.accept(list)
        }

        // 自定义地点不需要验证网络
        if poi.style == .customized {
            setPoid()
        } else {
            if shared.networkReachabilityType == .cellular || shared.networkReachabilityType == .ethernetOrWiFi {
                setPoid()
            } else {
                WmcHudManager.toast("设置失败，网络已断开连接")
            }
        }
    }

    /// 设置常用地址
    /// - Parameter poi: POI
    static func setCommonlyAddress(poi: AMapPOI) {
        let commonlyIndex = shared.commonlyAddressArray.firstIndex { model in
            poi.uid == model.uid
        }
        if let index = commonlyIndex, index < shared.commonlyAddressArray.count {
            shared.commonlyAddressArray.remove(at: index)
        } else {
            let m = WmcCommonlyAddressModel()
            m.uid = poi.uid
            m.name = poi.name
            m.address = poi.address
            m.province = poi.province
            m.city = poi.city
            m.district = poi.district
            m.latitude = poi.location.latitude
            m.longitude = poi.location.longitude

            shared.commonlyAddressArray.append(m)
        }

        let poiIndex = shared.poiList.value.firstIndex { model in
            poi.uid == model.poi.uid
        }
        if let poiIndex = poiIndex {
            var list = shared.poiList.value
            let model = list[poiIndex]
            model.style = model.style == .normal ? .commonly : .normal
            list[poiIndex] = model
            list = Self.sortCommonlyAddressList(list)
            shared.poiList.accept(list)
        }
    }

    /// 设置地址前缀
    static func setAddressPrefix(_ text: String) {
        shared.addressPrefix = text

        if let model = shared.poiStatus.value?.data {
            model.addressPrefix = text
            shared.poiStatus.accept((status: .updatePoiCompletion, data: model))
        }
    }

    /// 根据关键字搜索
    /// - Parameters:
    ///   - keyword: 关键字
    ///   - completion: 搜索成功
    ///   - failure: 搜索失败
    static func searchPoiByKeyword(_ keyword: String?, pageIndex: Int, pageSize: Int, location: CLLocation, radius: Int, completion: @escaping ([WmcSelectAddressModel]) -> Void, failure: @escaping (Error) -> Void) {
        WmcAMapSearchManager.requestNearbyPOI(location: location, keyword: keyword, radius: radius, pageSize: pageSize, pageIndex: pageIndex) { r in
            var list: [AMapPOI] = r.response?.pois ?? []

            if let request = r.request {
                var listTemp: [AMapPOI] = []

                list.forEach { poi in
                    let poiTemp = poi
                    let poiLocation = CLLocation(latitude: poiTemp.location.latitude, longitude: poiTemp.location.longitude)
                    let currentLocation = CLLocation(latitude: request.location.latitude, longitude: request.location.longitude)
                    let distance = currentLocation.distance(from: poiLocation)
                    poiTemp.distance = Int(distance)
                    listTemp.append(poiTemp)
                }

                list = listTemp
            }

            // 设置常用地址
            let array: [WmcSelectAddressModel] = list.map { poi in
                let isCommonly = WmcLocationManager.shared.queryIsCommonlyAddress(poi: poi)

                let model = WmcSelectAddressModel()
                model.poi = poi
                model.style = isCommonly ? .commonly : .normal
                return model
            }

            completion(array)

            if let request = r.request, pageIndex == 1, array.count == 0 {
                shared.trackSearchPoiEmptyEvent(request: request)
            }
        } failure: { r in
            failure(r.error)
        }
    }

    /// 地址排序
    /// - Parameter dataSource: 地址数组
    /// - Returns: 结果
    static func sortCommonlyAddressList(_ dataSource: [WmcSelectAddressModel]) -> [WmcSelectAddressModel] {
        var customList: [WmcSelectAddressModel] = []
        var commonlyList: [WmcSelectAddressModel] = []
        var notCommonlyList: [WmcSelectAddressModel] = []
        var result: [WmcSelectAddressModel] = []

        dataSource.forEach { model in
            if model.style == .customized {
                customList.append(model)
            } else if model.style == .commonly {
                commonlyList.append(model)
            } else {
                notCommonlyList.append(model)
            }
        }

        customList.sort { m1, m2 in
            return m1.poi.distance < m2.poi.distance
        }

        commonlyList.sort { m1, m2 in
            return m1.poi.distance < m2.poi.distance
        }

        notCommonlyList.sort { m1, m2 in
            return m1.poi.distance < m2.poi.distance
        }

        result.append(contentsOf: customList)
        result.append(contentsOf: commonlyList)
        result.append(contentsOf: notCommonlyList)

        return result
    }

    /// 标记选中的poi
    /// - Parameters:
    ///   - array: 数据源
    ///   - selectedPOI: poi
    /// - Returns: 新的数据源
    static func setSelectedAddress(array: [WmcSelectAddressModel], selectedPOI: WmcSelectAddressModel?) -> [WmcSelectAddressModel] {
        guard let selectedPOI = selectedPOI else { return array }

        let list: [WmcSelectAddressModel] = array.map { model in
            let temp = model
            temp.isSelected = temp.poi.uid == selectedPOI.poi.uid
            return temp
        }

        return list
    }

    /// 删除自定义定位点时需要重置地址
    func resetSelectedAddress() {
        manualSelectedPoi = nil
        addressPrefix = nil

        var array: [WmcSelectAddressModel] = []

        poiList.value.forEach { model in
            model.isSelected = false
            array.append(model)
        }

        let selectedPOI = queryDefaultPOI(array: array)

        // 设置默认地点的前缀
        selectedPOI?.addressPrefix = getDefaultAddressPrefix(poiData: selectedPOI?.poi)
        addressPrefix = selectedPOI?.addressPrefix

        // 数据源标记选中的POI
        array = WmcLocationManager.setSelectedAddress(array: array, selectedPOI: selectedPOI)

        // 发送POI列表
        poiList.accept(array)

        // 发送poi状态
        if selectedPOI != nil {
            poiStatus.accept((status: .updatePoiCompletion, data: selectedPOI))
        }
    }

    /// 地址前缀列表
    func getAddressPrefixList(poiData: AMapPOI? = nil) -> [String] {
        var poi = poiStatus.value?.data?.poi
        if poiData != nil {
            poi = poiData
        }

        var list: [String] = []
        if let poi = poi {
            if let city = poi.city {
                list.append(city)
            }
            if let district = poi.district {
                list.append(district)
            }
            if let province = poi.province, let city = poi.city, province != city {
                list.append("\(province)\(city)")
            }
            if let city = poi.city, let district = poi.district {
                list.append("\(city)\(district)")
            }
        }

        return list
    }

    func restartLocationService() {
        WmcAmapLocationManager.stopUpdateLocation()
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            WmcAmapLocationManager.startUpdateLocation()
        }
    }

    /// 设置默认选中的poi
    func queryDefaultPOI(array: [WmcSelectAddressModel]) -> WmcSelectAddressModel? {
        var result: WmcSelectAddressModel?

        if let model = array.first {
            model.addressPrefix = self.addressPrefix
            result = model
        }

        // 如果手动设置过poi， 且poi在范围内
        if let selectedPOI = manualSelectedPoi, let location = WmcAmapLocationManager.shared.location.value?.location {
            let distance = checkoutDistance(poi: selectedPOI.poi, location: location)
            if Int(distance) <= radius {
                result = selectedPOI
            }
        }

        if result == nil {
            result = defaultPoi
        }

        return result
    }

    // Private

    /// 当前城市（查天气）
    private var city: String = ""

    /// 设置poi搜索范围
    private var radius: Int = 200

    /// 常用地址
    private var commonlyAddressArray: [WmcCommonlyAddressModel] = []

    /// 自定义地址
    private var customAddressArray: [WmcCommonlyAddressModel] = []

    /// 是否需要刷新
    private var isNeededUpdate: Bool = false

    /// 网络状态
    private var networkReachabilityType: YPNetworkReachabilityType = .cellular

    /// 搜索poi页码
    private var pageIndex: Int = 1

    /// 搜索poi条数
    private var pageSize: Int = 50

    /// 记录手动选中的poi
    private var manualSelectedPoi: WmcSelectAddressModel?

    /// poi缓存列表
    private var poiCache: [WmcSelectAddressModel] = []

    /// 标记网络是否中断过
    private var networkInterruption = false

    /// 是否是常用地址
    /// - Parameter poi: POI
    /// - Returns: 结果
    private func queryIsCommonlyAddress(poi: AMapPOI) -> Bool {
        var isCommonly = false

        for (_, item) in commonlyAddressArray.enumerated() {
            if item.uid == poi.uid {
                isCommonly = true
                break
            }
        }

        return isCommonly
    }

    /// 查询常用地址
    /// - Parameter completion: 查询回调
    private func queryCommonlyAddress(completion: (([WmcCommonlyAddressModel]) -> Void)? = nil) {
        WmcCommonlyAddressDBManager.query { [weak self] list in
            guard let weakSelf = self else { return }
            weakSelf.commonlyAddressArray = list
            completion?(list)
        }
    }

    /// 查询自定义地址
    /// - Parameter completion: 查询回调
    private func queryCustomAddress(completion: (([WmcCommonlyAddressModel]) -> Void)? = nil) {
        WmcCustomAddressDBManager.query { [weak self] list in
            guard let weakSelf = self else { return }
            weakSelf.customAddressArray = list
            completion?(list)
        }
    }

    /// 默认地址前缀
    /// - Returns: 地址
    private func getDefaultAddressPrefix(poiData: AMapPOI? = nil) -> String? {
        // 当前显示的地址前缀，如果已经在地址前缀列表中，则直接返回当前显示的地址前缀，否则生成一个默认的地址前缀
        let addressPrefixList = self.getAddressPrefixList(poiData: poiData)
        if let addressPrefix = addressPrefix, addressPrefixList.contains(addressPrefix) {
            return addressPrefix
        }

        var poi = poiStatus.value?.data?.poi
        if poiData != nil {
            poi = poiData
        }

        var prefix: String?

        if let poi = poi {
            if let city = poi.city, let district = poi.district {
                prefix = "\(city)\(district)"
            } else if let province = poi.province, let city = poi.city {
                prefix = "\(province)\(city)"
            } else if let city = poi.city {
                prefix = city
            } else if let district = poi.district {
                prefix = district
            }
        }

        return prefix
    }

    /// 自动刷新poi
    private func autoUpdatePOI() {
        guard let location = WmcAmapLocationManager.shared.location.value?.location else { return }

        poiStatus.accept((status: .updatingPoi, data: nil))

        WmcAutoUpdatePoiManager.requestNearbyPOI(location: location, keyword: nil, radius: radius, pageSize: pageSize, pageIndex: pageIndex) { [weak self] r in
            guard let weakSelf = self else { return }
            let list: [AMapPOI] = r.response?.pois ?? []

            // 设置常用地址
            var array: [WmcSelectAddressModel] = list.map { poi in
                let isCommonly = WmcLocationManager.shared.queryIsCommonlyAddress(poi: poi)

                let model = WmcSelectAddressModel()
                model.poi = poi
                model.style = isCommonly ? .commonly : .normal
                return model
            }

            // 添加缓存数据
            array = weakSelf.appendPoiCache(list: array)

            // 排序
            array = WmcLocationManager.sortCommonlyAddressList(array)

            // 查询默认选中的POI
            let selectedPOI = weakSelf.queryDefaultPOI(array: array)

            // 设置默认地点的前缀
            selectedPOI?.addressPrefix = weakSelf.getDefaultAddressPrefix(poiData: selectedPOI?.poi)
            weakSelf.addressPrefix = selectedPOI?.addressPrefix

            // 数据源标记选中的POI
            array = WmcLocationManager.setSelectedAddress(array: array, selectedPOI: selectedPOI)

            // 发送POI列表
            weakSelf.poiList.accept(array)

            // 发送poi状态
            if selectedPOI != nil {
                weakSelf.poiStatus.accept((status: .updatePoiCompletion, data: selectedPOI))
            } else {
                weakSelf.poiStatus.accept((status: .updatePoiFailure, data: nil))
                weakSelf.clean()
            }

            if array.isEmpty {
                weakSelf.trackSearchPoiEmptyEvent(request: r.request)
            }
            weakSelf.firstFetchPoi = false
        } failure: { [weak self] r in
            guard let weakSelf = self else { return }
            weakSelf.poiStatus.accept((status: .updatePoiFailure, data: nil))
            weakSelf.clean()
            weakSelf.trackSearchPoiFailureEvent(request: r.request, error: r.error)
            weakSelf.firstFetchPoi = false
        }
    }

    private func clean() {
        poiList.accept([])
        manualSelectedPoi = nil
        addressPrefix = nil
        WmcAmapLocationManager.clean()
    }

    /// 处理poi缓存数据
    /// - Parameter list: 最新列表
    /// - Returns: 混合了缓存数据的poi列表
    private func appendPoiCache(list: [WmcSelectAddressModel]) -> [WmcSelectAddressModel] {
        var cacheTemp: [WmcSelectAddressModel] = poiCache

        list.forEach { model in
            let index = poiCache.firstIndex { cacheModel in
                return model.poi.uid == cacheModel.poi.uid
            }
            if index == nil {
                cacheTemp.append(model)
            }
        }

        poiCache = cacheTemp
        cacheTemp.removeAll()

        poiCache.forEach { model in
            guard let location = WmcAmapLocationManager.shared.location.value?.location else {
                return
            }
            let modelTemp = model
            // 重新计算距离
            let distance = self.checkoutDistance(poi: model.poi, location: location)
            modelTemp.poi.distance = Int(distance)
            cacheTemp.append(modelTemp)
        }

        var result: [WmcSelectAddressModel] = []

        poiCache.forEach { model in
            if model.poi.distance <= self.radius {
                result.append(model)
            }
        }

        return result
    }

    /// 计算poi和当前位置的距离
    /// - Parameters:
    ///   - poi: poi
    ///   - location: 当前位置
    /// - Returns: 距离
    private func checkoutDistance(poi: AMapPOI, location: CLLocation) -> Double {
        guard let point = poi.location else { return 0.0 }
        let firstLocation = CLLocation(latitude: point.latitude, longitude: point.longitude)
        let distance: Double = firstLocation.distance(from: location)
        return distance
    }

    private func bindEvent() {
        WmcAmapLocationManager.shared.location.subscribe(onNext: { [weak self] model in
            guard let weakSelf = self else { return }

            if let status = model?.status, (status == .fetching || status == .failure), WmcAuthorizedManager.location().info.isAvailable {
                weakSelf.locationStatus.accept((status: status, data: nil))
                return
            }

            guard let status = model?.status, let location = model?.location else { return }

            WmcAMapSearchManager.requestReGeocode(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude) { [weak self] reGeocode in
                guard let weakSelf = self else { return }

                let locationModel = WmcLocationModel()
                locationModel.location = model?.location
                locationModel.reGeocode = reGeocode

                // 转发定位状态
                weakSelf.locationStatus.accept((status: status, data: locationModel))

                weakSelf.defaultPoi = weakSelf.createDefaultPoi(location: location, reGeocode: reGeocode)

                // 刷新poi数据
                weakSelf.autoUpdatePOI()

                // 查询天气
                if let city = reGeocode.addressComponent.city {
                    // 城市发生变化
                    if !city.isEmpty, weakSelf.city != city {
                        weakSelf.city = city

                        WmcAMapSearchManager.requestWeather(city: city) { result in
                            WmcWeatherManager.share.weather.accept(result)
                        }
                    }
                }
            } failure: { _ in
                weakSelf.locationStatus.accept((status: .failure, data: nil))
                weakSelf.firstFetchPoi = false
            }
        }).disposed(by: rx.disposeBag)

        YPNetworkReachability.monitoring().subscribe(onNext: { [weak self] type in
            guard let weakSelf = self else { return }
            weakSelf.networkReachabilityType = type

            if type == .cellular || type == .ethernetOrWiFi {
                if weakSelf.networkInterruption {
                    weakSelf.restartLocationService()
                    weakSelf.networkInterruption = false
                }
            } else {
                // 如果是自定义地点，断网也可以显示
                if weakSelf.poiStatus.value?.data?.style == .customized {
                    return
                }
                weakSelf.networkInterruption = true
                weakSelf.poiStatus.accept((status: .updatePoiFailure, data: nil))
                weakSelf.clean()
                weakSelf.trackSearchPoiNotNetwork()
            }
        }).disposed(by: rx.disposeBag)

        NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
    }

    private func createDefaultPoi(location: CLLocation, reGeocode: AMapReGeocode) -> WmcSelectAddressModel {
        if let poi = reGeocode.pois.first {
            poi.address = reGeocode.addressComponent.township ?? ""
            poi.distance = 0
            poi.province = reGeocode.addressComponent.province ?? ""
            poi.city = reGeocode.addressComponent.city ?? ""
            poi.district = reGeocode.addressComponent.district ?? ""
            let addressModel = WmcSelectAddressModel()
            addressModel.poi = poi
            return addressModel
        } else {
            let poi: AMapPOI = .init()
            poi.location = AMapGeoPoint.location(withLatitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
            poi.uid = UUID().uuidString
            poi.name = reGeocode.addressComponent.township ?? ""
            poi.address = reGeocode.addressComponent.township ?? ""
            poi.distance = 0
            poi.province = reGeocode.addressComponent.province ?? ""
            poi.city = reGeocode.addressComponent.city ?? ""
            poi.district = reGeocode.addressComponent.district ?? ""
            let addressModel = WmcSelectAddressModel()
            addressModel.poi = poi
            return addressModel
        }
    }

    @objc
    private func appWillEnterForeground() {
        let authorization = WmcAuthorizedManager.location()

        locationAuthorizationPublish.accept(authorization.info.isAvailable)

        if YPWmcManager.shared.platform != .watermarkCamera {
            return
        }

        if authorization.info.isAvailable {
            WmcAmapLocationManager.startUpdateLocation()
        } else {
            if WmcAmapLocationManager.shared.lastLocation != nil {
                WmcAmapLocationManager.clean()
                locationStatus.accept((status: .failure, data: nil))
                poiList.accept([])
            }
            WmcTrack.addEvent(id: "location_authorization", params: ["custom_status": authorization])
        }
    }

    @objc
    private func appWillEnterBackground() {
        WmcAmapLocationManager.stopUpdateLocation()
    }
}

extension WmcLocationManager {
    private func trackSearchPoiEmptyEvent(request: AMapPOISearchBaseRequest) {
        var params: [String: Any] = ["description": request.formattedDescription() ?? ""]
        let time = WmcDateManager.shared.networkDate.value ?? Date()
        let timeStr = time.yp.string("yyyy-MM-dd hh:mm:ss")
        params["time"] = timeStr
        WmcTrack.addEvent(id: "poi_search_empty", params: params)
    }

    private func trackSearchPoiFailureEvent(request: AMapPOIAroundSearchRequest, error: Error) {
        var params: [String: Any] = [:]
        params["latitude"] = request.location.latitude
        params["longitude"] = request.location.longitude
        params["keywords"] = request.keywords
        params["radius"] = request.radius
        params["offset"] = request.offset
        params["page"] = request.page

        let time = WmcDateManager.shared.networkDate.value ?? Date()
        let timeStr = time.yp.string("yyyy-MM-dd hh:mm:ss")
        params["time"] = timeStr
        if let accountModel = WmcLoginManager.shared.wmcAccountModel {
            params["yp"] = accountModel.yupaoId
            params["wmc"] = accountModel.memberId
        }
        params["error"] = error.localizedDescription

        WmcTrack.addEvent(id: "poi_search_failure", params: params)
    }

    private func trackSearchPoiNotNetwork() {
        var params: [String: Any] = [:]
        let time = WmcDateManager.shared.networkDate.value ?? Date()
        let timeStr = time.yp.string("yyyy-MM-dd hh:mm:ss")
        params["time"] = timeStr
        WmcTrack.addEvent(id: "poi_search_not_network", params: params)
    }
}
